﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;

namespace ReactiveCode.Expressions
{
    [System.Diagnostics.DebuggerTypeProxy(typeof(Block.DebugView))]
    class Block : ReactiveExpression, IExpressionParent
    {
        #region DebugView

        class DebugView
        {
            [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
            public ReactiveExpression[] Children;

            public DebugView(Block block)
            {
                Contract.Requires(block != null);
                Children = block.Body.ToArray();
            }
        }

        #endregion

        class ExpressionCollection : Collection<ReactiveExpression>
        {
            Block _owner;
            public ExpressionCollection(Block owner)
            {
                _owner = owner;
            }

            void OnInserting(ReactiveExpression expr)
            {
                Contract.Requires(expr != null && expr.Parent == null);
            }
            void OnRemoved(ReactiveExpression expr)
            {
                expr.Parent = null;
                expr.Enabled = false;
            }

            protected override void InsertItem(int index, ReactiveExpression expr)
            {
                OnInserting(expr);
                base.InsertItem(index, expr);
                expr.Parent = _owner;

                if (index <= _owner.DisableAfter)
                {
                    _owner._disableAfter++;
                    expr.Enabled = true;
                }
                else
                {
                    expr.Enabled = false;
                }
            }
            protected override void SetItem(int index, ReactiveExpression expr)
            {
                OnInserting(expr);

                var old = this[index];
                bool wasEnabled = old.Enabled;
                base.SetItem(index, expr);
                OnRemoved(old);

                expr.Parent = _owner;
                expr.Enabled = wasEnabled;
            }
            protected override void RemoveItem(int index)
            {
                var expr = this[index];
                base.RemoveItem(index);
                OnRemoved(expr);

                if (index < _owner.DisableAfter)
                {
                    _owner._disableAfter--;
                }
                else if (index == _owner.DisableAfter)
                {
                    Contract.Assert(_owner.Disablers.First() == expr);
                    _owner.UpdateDisableAfter();
                }
            }
            protected override void ClearItems()
            {
                var exprs = this.ToList();
                base.ClearItems();
                foreach (var expr in exprs)
                    OnRemoved(expr);
                _owner.UpdateDisableAfter();
            }
        }

        public ObservableCollection<ReactiveExpression> Disablers { get; private set; }

        int _disableAfter;
        int DisableAfter
        {
            get { return _disableAfter; }
            set
            {
                if (_disableAfter == value)
                    return;
                _disableAfter = value;
                for (int i = 0; i < Body.Count; i++)
                    Body[i].Enabled = i <= value;
            }
        }
        void UpdateDisableAfter()
        {
            if (!Enabled)
                DisableAfter = -1;
            else 
                DisableAfter = Disablers.Select(Body.IndexOf).Where(i => i >= 0)
                    .Min(i => (int?)i) ?? int.MaxValue;
        }

        public Collection<ReactiveExpression> Body { get; private set; }
        IList<ReactiveExpression> IExpressionParent.Children
        {
            get { return Body; }
        }

        public Block()
        {
            Body = new ExpressionCollection(this);
            Disablers = new ObservableCollection<ReactiveExpression>();
            EnabledProperty.Subscribe(_ => UpdateDisableAfter());
            Disablers.CollectionChanged += (s, e) => UpdateDisableAfter();
        }
        public override void Dispose()
        {
            base.Dispose();
            foreach (var expr in Body)
                expr.Dispose();
        }

        public override string ToString()
        {
            return "Block";
        }
    }
}
